﻿using System;
using System.Collections.Specialized;
using System.ComponentModel;
using System.Drawing.Design;
using System.Reflection;
using System.Security.Permissions;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;

[assembly: WebResource("ASPControlsExtended.Controls.EmbeddedResources.arrowdown.png", "image/png")]
[assembly: WebResource("ASPControlsExtended.Controls.EmbeddedResources.arrowup.png", "image/png")]

namespace ASPControlsExtended.Controls {

    /// <summary>
    /// Extended System.Web.UI.WebControls.GridView control
    /// </summary>
    [
    AspNetHostingPermission(SecurityAction.Demand, Level = AspNetHostingPermissionLevel.Minimal),
    AspNetHostingPermission(SecurityAction.InheritanceDemand, Level = AspNetHostingPermissionLevel.Minimal),
    DefaultEvent("SelectedIndexChanged"),
    SupportsEventValidation,
    ToolboxData("<{0}:GridView runat=\"server\" />"),
    Themeable(true),
    Designer("System.Web.UI.Design.WebControls.GridViewDesigner, System.Design, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a")
    ]
    public class GridView : System.Web.UI.WebControls.GridView {

        #region Properties

        #region Styles

        private TableItemStyle _sortAscendingStyle = null;

        /// <summary>
        /// The style applied to the header cell that is sorted in ascending order.
        /// </summary>
        [
        Bindable(false)
        ]
        public TableItemStyle SortAscendingStyle {
            get {
                if (_sortAscendingStyle != null) {
                    return _sortAscendingStyle;
                }

                _sortAscendingStyle = new TableItemStyle();
                if (base.IsTrackingViewState)
                    ((IStateManager)_sortAscendingStyle).TrackViewState();

                return _sortAscendingStyle;
            }
        }

        private TableItemStyle _sortDescendingStyle = null;

        /// <summary>
        /// The style applied to the header cell that is sorted in descending order.
        /// </summary>
        [
        Bindable(true),
        Category("Styles"),
        DefaultValue(""),
        Description("The style applied to the header cell that is sorted in descending order."),
        ]
        public TableItemStyle SortDescendingStyle {
            get {
                if (_sortDescendingStyle != null) {
                    return _sortDescendingStyle;
                }

                _sortDescendingStyle = new TableItemStyle();
                if (base.IsTrackingViewState)
                    ((IStateManager)_sortDescendingStyle).TrackViewState();

                return _sortDescendingStyle;
            }
        }

        #endregion Styles

        #region Misc

        private int _recordCount;

        [Description("Item Count Text to be displayed. e.g. Showing {0} to {1} out of {2} items. {0} = Start Index, {1} = End Index, {2} = Total Count")]
        public string ItemCountText {
            get {
                object o = ViewState[string.Format("{0}{1}", this.ID, "ItemCountText")];
                return (string)(o ?? (AllowPaging ? "Showing {0} to {1} out of {2} items." : "Showing {2} items."));
            }
            set {
                ViewState[string.Format("{0}{1}", this.ID, "ItemCountText")] = value;
            }
        }

        private string _itemCountCssClass;
        [
        Themeable(true),
        Bindable(true),
        Category("Behavior"),
        DefaultValue(true),
        Description("Specify CssClass for Record Count text.")
        ]
        public string ItemCountCssClass {
            get {
                return _itemCountCssClass ?? "gridview__recordCount";
            }
            set {
                _itemCountCssClass = value;
            }
        }

        /// <summary>
        /// Get or Set the Current Sort Expression.
        /// </summary>
        [
        Themeable(false),
        Bindable(true),
        Category("Behavior"),
        DefaultValue(""),
        Description("Get or set Current Sort Expression text.")
        ]
        public string CurrentSortExpression {
            get {
                object o = ViewState[string.Format("{0}{1}", this.ID, "SortExpression")];

                string value = o as string;
                if (value == null || value.Trim().Length == 0)
                    return InitialSortExpression;
                else
                    return value;
            }
            set {
                ViewState[string.Format("{0}{1}", this.ID, "SortExpression")] = value;
            }
        }

        /// <summary>
        /// Get or Set the Current Sort Direction.
        /// </summary>
        [
        Themeable(false),
        Bindable(true),
        Category("Behavior"),
        DefaultValue(SortDirection.Ascending),
        Description("Get or set Current Sort Direction.")
        ]
        public SortDirection CurrentSortDirection {
            get {
                object o = ViewState[string.Format("{0}{1}", this.ID, "SortDirection")];
                if (o == null)
                    return InitialSortDirection;
                else
                    return (SortDirection)o;
            }
            set {
                ViewState[string.Format("{0}{1}", this.ID, "SortDirection")] = value;
            }
        }

        private string _defaultSortExpression = null;

        /// <summary>
        /// Get or Set the Sort Expression when the GridView is databound first.
        /// </summary>
        [
        Themeable(false),
        Bindable(true),
        Category("Behavior"),
        DefaultValue(""),
        Description("Get or Set Current Sort Direction.")
        ]
        public string InitialSortExpression {
            get {
                if (!string.IsNullOrWhiteSpace(_defaultSortExpression)) {
                    return _defaultSortExpression;
                }

                if (this.Columns.Count == 0) {
                    return "";
                }

                foreach (DataControlField col in this.Columns) {
                    if (!string.IsNullOrWhiteSpace(col.SortExpression)) {
                        return col.SortExpression;
                    }
                }

                return string.Empty;
            }
            set {
                _defaultSortExpression = value;
            }
        }

        /// <summary>
        /// Get or Set the Sort Direction when the GridView is databound first.
        /// </summary>
        [
        Themeable(false),
        Bindable(true),
        Category("Behavior"),
        DefaultValue(SortDirection.Ascending),
        Description("Get or Set Current Sort Direction.")
        ]
        public SortDirection InitialSortDirection { get; set; }

        /// <summary>
        /// Get or Set value indicating whether to highlight row on mousehover.
        /// </summary>
        [
        Themeable(true),
        Bindable(true),
        Category("Behavior"),
        DefaultValue(false),
        Description("Specify whether to show record count and number of items being shown.")
        ]
        public bool ItemCountVisible { get; set;}

        /// <summary>
        /// Get or Set value indicating whether Sort Arrows are visible.
        /// </summary>
        [
        Themeable(true),
        Bindable(true),
        Category("Behavior"),
        DefaultValue(true),
        Description("Specify whether to show column sort arrow image.")
        ]
        public bool SortArrowVisible { get; set;}

        #endregion Misc

        #region ArrowUpImageUrl

        private string _arrowUpImageURL;

        /// <summary>
        /// Get or Set the Up Arrow image URL.
        /// </summary>
        [
        Bindable(true),
        Category("Behavior"),
        DefaultValue(true),
        Description("The url of the image shown when a column is sorted in ascending order."),
        UrlProperty(),
        Editor("System.Web.UI.Design.UrlEditor", typeof(UITypeEditor))
        ]
        public string ArrowUpImageUrl {
            get {
                if (this._arrowUpImageURL == null || this._arrowDownImageURL.Trim().Length > 0)
                    return Page.ClientScript.GetWebResourceUrl(this.GetType(), "ASPControlsExtended.Controls.EmbeddedResources.arrowup.png");
                else
                    return _arrowUpImageURL;
            }
            set {
                _arrowUpImageURL = value;
            }
        }

        #endregion ArrowUpImageUrl

        #region ArrowDownImageUrl

        private string _arrowDownImageURL;

        /// <summary>
        /// Get or Set Down Arrow image URL.
        /// </summary>
        [
        Bindable(true),
        Category("Behavior"),
        DefaultValue(true),
        Description("Get URL of the image shown when a column is sorted in descending order."),
        UrlProperty(),
        Editor("System.Web.UI.Design.UrlEditor", typeof(UITypeEditor))
        ]
        public string ArrowDownImageUrl {
            get {
                if (this._arrowDownImageURL == null || this._arrowDownImageURL.Trim().Length == 0) {
                    return Page.ClientScript.GetWebResourceUrl(this.GetType(), "ASPControlsExtended.Controls.EmbeddedResources.arrowdown.png");
                } else {
                    return _arrowDownImageURL;
                }
            }
            set {
                _arrowDownImageURL = value;
            }
        }

        #endregion ArrowDownImageUrl

        /// <summary>
        /// Get or Set the Arrow Position with regards to the current contents on the header.
        /// </summary>
        [
        Bindable(true),
        Category("Behavior"),
        DefaultValue(Position.Bottom),
        Description("Specify the location of the sort arrow.")
        ]
        public Position ArrowPosition { get; set;}

        #endregion Properties

        #region Methods

        /// <summary>
        /// Set Sort Expression and Direction
        /// </summary>
        /// <param name="newSortExpression">New sort expression</param>
        /// <remarks>If sorting is done on the column that was sorted last, only direction will be changed. Otherwise, sorting expression will be set as the one send in and direction will be ascending</remarks>
        private void SetSortProperties(string newSortExpression) {

            //If sorting on the column that was sorted previously, only change the direction, Else make the direction Descending.
            if (CurrentSortExpression == newSortExpression)
                if (CurrentSortDirection == SortDirection.Ascending)
                    CurrentSortDirection = SortDirection.Descending;
                else
                    CurrentSortDirection = SortDirection.Ascending;

            else
                CurrentSortDirection = SortDirection.Ascending;

            CurrentSortExpression = newSortExpression;
        }

        public T GetDatakeyValues<T>(int rowIndex) {
            IOrderedDictionary datakeyDictionary = this.DataKeys[rowIndex].Values as IOrderedDictionary;
            if (datakeyDictionary == null) {
                return default(T);
            }

            T dataKeyEntity = Activator.CreateInstance<T>();

            var dataKeyEnumerator = datakeyDictionary.GetEnumerator();

            while (dataKeyEnumerator.MoveNext()) {
                var current = dataKeyEnumerator.Entry;
                var propertyInfo = dataKeyEntity.GetType().GetProperty(current.Key.ToString(), BindingFlags.Public | BindingFlags.Instance);
                if (propertyInfo == null) {
                    continue;
                }

                propertyInfo.SetValue(dataKeyEntity, current.Value, null);
            }

            return dataKeyEntity;
        }

        /// <summary>
        /// Prepare ControlHierarchy. Add sort arrows if specified.
        /// </summary>
        /// <remarks>
        /// Code Source: http://www.4guysfromrolla.com/articles/020608-1.aspx 
        /// </remarks>
        protected override void PrepareControlHierarchy() {
            base.PrepareControlHierarchy();

            if (!this.HasControls()) {
                return;
            }

            Table table = this.Controls[0] as Table;
            if (table == null || table.Rows.Count == 0) {
                return;
            }

            if (!this.SortArrowVisible || string.IsNullOrWhiteSpace(this.CurrentSortExpression)|| !this.ShowHeader) {
                return;
            }

            // Need to check first TWO rows because the first row may be a
            // pager row... Thanks for Barbaros Sağlamtimur for this catch!
            GridViewRow headerRow = table.Rows[0] as GridViewRow;
            if (headerRow.RowType != DataControlRowType.Header && table.Rows.Count > 1)
                headerRow = table.Rows[1] as GridViewRow;

            if (headerRow.RowType != DataControlRowType.Header) {
                return;
            }

            foreach (TableCell cell in headerRow.Cells) {
                DataControlFieldCell gridViewCell = cell as DataControlFieldCell;
                if (gridViewCell == null) {
                    continue;
                }

                DataControlField cellsField = gridViewCell.ContainingField;
                if (cellsField == null || cellsField.SortExpression != this.CurrentSortExpression) {
                    continue;
                }

                // Add the sort arrows for this cell
                CreateSortArrows(cell);

                // We're done!
                break;
            }
        }

        private void AddRecordCountText(TableCell cell) {
            if (cell == null) {
                return;
            }

            Literal literal = new Literal();
            literal.Text = GetRecordCountText();

            Table pagingTable = cell.Controls != null && cell.Controls.Count > 0 ? cell.Controls[0] as Table : null;
            if (pagingTable == null) {
                pagingTable = new Table();
                TableRow row = new TableRow();
                TableCell tableCell = new TableCell();
                tableCell.Controls.Add(literal);
                row.Controls.Add(tableCell);
                pagingTable.Controls.Add(row);
                cell.Controls.Add(pagingTable);
            } else {
                TableRow tableRow = pagingTable.Rows[0] as TableRow;
                if (tableRow == null) {
                    return;
                }
                TableCell tableCell = new TableCell();
                tableCell.Controls.Add(literal);
                tableRow.Controls.AddAt(0, tableCell);
            }

        }

        private string GetRecordCountText() {
            string message = "";
            if (AllowPaging) {
                int startIndex = this.PageIndex * this.PageSize + 1;
                int lastIndex = (this.PageIndex == this.PageCount - 1) ? _recordCount : startIndex + this.PageSize - 1;
                message = ItemCountText.Replace("{0}", startIndex.ToString()).Replace("{1}", lastIndex.ToString()).Replace("{2}", _recordCount.ToString());
            } else {
                message = ItemCountText.Replace("{0}", "").Replace("{1}", "").Replace("{2}", this.Rows.Count.ToString());
            }
            return string.Format("<div class='{1}'>{0}</div>", message, ItemCountCssClass);
        }

        /// <summary>
        /// On Sorting
        /// </summary>
        /// <param name="e"></param>
        protected override void OnSorting(GridViewSortEventArgs e) {
            this.PageIndex = 0;
            this.SelectedIndex = -1;
            this.SetSortProperties(e.SortExpression);
            base.OnSorting(e);
        }

        protected override void OnPageIndexChanging(GridViewPageEventArgs e) {
            this.PageIndex = e.NewPageIndex;
            this.SelectedIndex = -1;
            base.OnPageIndexChanging(e);
        }

        protected override void InitializePager(GridViewRow row, int columnSpan, PagedDataSource pagedDataSource) {
            base.InitializePager(row, columnSpan, pagedDataSource);
            this._recordCount = pagedDataSource.DataSourceCount;
        }

        protected override void Render(HtmlTextWriter writer) {

            if (!this.ItemCountVisible || this.Controls == null || this.Controls.Count < 1) {
                base.Render(writer);
                return;
            }

            Table table = this.Controls[0] as Table;
            if (table == null || table.Rows.Count == 0) {
                base.Render(writer);
                return;
            }

            bool pagerRowVisible = this._recordCount > this.PageSize;
            bool addTop = (!pagerRowVisible | this.PagerSettings.Position == PagerPosition.Bottom);
            bool addBottom = (!pagerRowVisible | this.PagerSettings.Position == PagerPosition.Top);

            if (pagerRowVisible) {
                if (this.PagerSettings.Position == PagerPosition.Top || this.PagerSettings.Position == PagerPosition.TopAndBottom) {
                    AddRecordCountTextToPagerRow(table, 0);
                }
                if (this.PagerSettings.Position == PagerPosition.Bottom || this.PagerSettings.Position == PagerPosition.TopAndBottom) {
                    AddRecordCountTextToPagerRow(table, table.Rows.Count - 1);
                }
            }

            if (addTop) {
                AddPagerRow(table, PagerPosition.Top);
            }
            if (addBottom) {
                AddPagerRow(table, PagerPosition.Bottom);
            }

            base.Render(writer);
        }

        private void AddPagerRow(Table table, PagerPosition pagerPosition) {
            GridViewRow gridViewRow = new GridViewRow(-1, -1, DataControlRowType.Pager, DataControlRowState.Normal);
            TableCell tableCell = new TableCell();
            tableCell.ColumnSpan = this.Columns.Count;
            AddRecordCountText(tableCell);
            gridViewRow.Cells.Add(tableCell);
            if (pagerPosition == PagerPosition.Top) {
                table.Rows.AddAt(0, gridViewRow);
            } else {
                table.Rows.Add(gridViewRow);
            }
        }

        private void AddRecordCountTextToPagerRow(Table table, int rowIndex) {
            GridViewRow pagerHeadRow = table.Rows[rowIndex] as GridViewRow;
            if (pagerHeadRow != null && pagerHeadRow.RowType == DataControlRowType.Pager) {
                AddRecordCountText(pagerHeadRow.Cells[0]);
            }
        }

        /// <summary>
        /// Add Sort Arrows to column being sorted.
        /// </summary>
        /// <param name="sortedCell">TableCell</param>
        /// <remarks>
        /// Source: http://www.4guysfromrolla.com/articles/020608-1.aspx (modified to render Arrow image based on ArrowPosition property value.)
        /// </remarks>
        protected virtual void CreateSortArrows(TableCell sortedCell) {

            // Add the appropriate arrow image and apply the appropriate state, depending on whether we're
            // sorting the results in ascending or descending order
            string imageId = this.ID + "_image";
            Control image = sortedCell.FindControl(imageId);
            if (image != null) {
                return;
            }

            TableItemStyle sortStyle = null;
            using (Image arrow = new Image()) {
                arrow.ID = imageId;

                if (this.CurrentSortDirection == SortDirection.Ascending) {
                    arrow.ImageUrl = this.ArrowUpImageUrl;
                    sortStyle = _sortAscendingStyle;
                    arrow.AlternateText = "Ascending";
                } else {
                    arrow.ImageUrl = this.ArrowDownImageUrl;
                    sortStyle = _sortDescendingStyle;
                    arrow.AlternateText = "Descending";
                }

                //arrow.ImageUrl = imgUrl;
                arrow.BorderStyle = BorderStyle.None;
                Literal ltl = new Literal();
                ltl.Text = "<br />";
                string sortExpression = String.Empty;

                switch (this.ArrowPosition) {
                    case Position.Bottom:

                        //Add the arrow as last control
                        arrow.Style.Add(HtmlTextWriterStyle.MarginTop, "2px");
                        if (this.AllowSorting) {
                            sortedCell.Controls.AddAt(sortedCell.Controls.Count, ltl);
                            sortedCell.Controls.AddAt(sortedCell.Controls.Count, arrow);
                        } else {
                            sortExpression = sortedCell.Text;
                            using (ltl = new Literal()) {
                                ltl.Text = sortExpression;
                                sortedCell.Controls.AddAt(0, ltl);
                            }
                            using (ltl = new Literal()) {
                                ltl.Text = "<br >";
                                sortedCell.Controls.AddAt(1, ltl);
                                sortedCell.Controls.AddAt(2, arrow);
                            }
                        }
                        break;

                    case Position.Left:
                        arrow.Style.Add(HtmlTextWriterStyle.MarginRight, "2px");
                        if (this.AllowSorting) {
                            using (ltl = new Literal()) {
                                ltl.Text = "</td><td>";
                                sortedCell.Controls.AddAt(0, ltl);
                                sortedCell.Controls.AddAt(0, arrow);
                            }
                            using (ltl = new Literal()) {
                                ltl.Text = "<table class='SortedCell' cellpadding='0' cellspacing='0'><tr><td>";
                                sortedCell.Controls.AddAt(0, ltl);
                            }
                            using (ltl = new Literal()) {
                                ltl.Text = "</td></tr></table>";
                                sortedCell.Controls.AddAt(sortedCell.Controls.Count, ltl);
                            }
                        } else {
                            sortExpression = sortedCell.Text;
                            using (ltl = new Literal()) {
                                ltl.Text = "</td></tr></table>";
                                sortedCell.Controls.AddAt(0, ltl);
                            }
                            using (ltl = new Literal()) {
                                ltl.Text = sortExpression;
                                sortedCell.Controls.AddAt(0, ltl);
                            }
                            using (ltl = new Literal()) {
                                ltl.Text = "</td><td>";
                                sortedCell.Controls.AddAt(0, ltl);
                                sortedCell.Controls.AddAt(0, arrow);
                            }
                            using (ltl = new Literal()) {
                                ltl.Text = "<table class='SortedCell' cellpadding='0' cellspacing='0'><tr><td>";
                                sortedCell.Controls.AddAt(0, ltl);
                            }
                        }
                        break;

                    case Position.Right:
                        arrow.Style.Add(HtmlTextWriterStyle.MarginLeft, "2px");
                        if (this.AllowSorting) {
                            using (ltl = new Literal()) {
                                ltl.Text = "<table class='SortedCell' cellpadding='0' cellspacing='0'><tr><td>";
                                sortedCell.Controls.AddAt(0, ltl);
                            }
                            using (ltl = new Literal()) {
                                ltl.Text = "</td><td>";
                                sortedCell.Controls.AddAt(sortedCell.Controls.Count, ltl);
                                sortedCell.Controls.AddAt(sortedCell.Controls.Count, arrow);
                            }
                            using (ltl = new Literal()) {
                                ltl.Text = "</td></tr></table>";
                                sortedCell.Controls.AddAt(sortedCell.Controls.Count, ltl);
                            }
                        } else {
                            sortExpression = sortedCell.Text;
                            using (ltl = new Literal()) {
                                ltl.Text = "</td></tr></table>";
                                sortedCell.Controls.AddAt(0, ltl);
                                sortedCell.Controls.AddAt(0, arrow);
                            }
                            using (ltl = new Literal()) {
                                ltl.Text = "</td><td>";
                                sortedCell.Controls.AddAt(0, ltl);
                            }
                            using (ltl = new Literal()) {
                                ltl.Text = sortExpression;
                                sortedCell.Controls.AddAt(0, ltl);
                            }
                            using (ltl = new Literal()) {
                                ltl.Text = "<table class='SortedCell' cellpadding='0' cellspacing='0'><tr><td>";
                                sortedCell.Controls.AddAt(0, ltl);
                            }
                        }
                        break;

                    case Position.Top:

                        //Add the arrow as the first control
                        arrow.Style.Add(HtmlTextWriterStyle.MarginBottom, "2px");
                        if (this.AllowSorting) {
                            sortedCell.Controls.AddAt(0, arrow);
                            sortedCell.Controls.AddAt(1, ltl);
                        } else {
                            sortExpression = sortedCell.Text;
                            sortedCell.Controls.AddAt(0, arrow);
                            sortedCell.Controls.AddAt(1, ltl);
                            using (ltl = new Literal()) {
                                ltl.Text = sortExpression;
                                sortedCell.Controls.AddAt(2, ltl);
                            }
                        }
                        break;
                }

                if (sortStyle != null)
                    sortedCell.MergeStyle(sortStyle);
            }
        }

        #region State Management Methods

        /// <summary>
        /// </summary>
        /// <returns></returns>
        /// <remarks>
        /// Source: http://www.4guysfromrolla.com/articles/020608-1.aspx (no modifications)
        /// </remarks>
        protected override object SaveViewState() {

            // We need to save any programmatic changes to the SortAscendingStyle or SortDescendingStyle
            // properties to view state...
            object[] state = new object[3];
            state[0] = base.SaveViewState();
            if (_sortAscendingStyle != null)
                state[1] = ((IStateManager)_sortAscendingStyle).SaveViewState();
            if (_sortDescendingStyle != null)
                state[2] = ((IStateManager)_sortDescendingStyle).SaveViewState();

            return state;
        }

        /// <summary>
        /// </summary>
        /// <returns></returns>
        /// <remarks>
        /// Source: http://www.4guysfromrolla.com/articles/020608-1.aspx (no modifications)
        /// </remarks>
        protected override void LoadViewState(object savedState) {
            object[] state = (object[])savedState;

            base.LoadViewState(state[0]);

            if (state[1] != null)
                ((IStateManager)this.SortAscendingStyle).LoadViewState(state[1]);
            if (state[2] != null)
                ((IStateManager)this.SortDescendingStyle).LoadViewState(state[2]);
        }

        /// <summary>
        /// </summary>
        /// <returns></returns>
        /// <remarks>
        /// Source: http://www.4guysfromrolla.com/articles/020608-1.aspx (no modifications)
        /// </remarks>
        protected override void TrackViewState() {
            base.TrackViewState();

            if (_sortAscendingStyle != null)
                ((IStateManager)_sortAscendingStyle).TrackViewState();
            if (_sortDescendingStyle != null)
                ((IStateManager)_sortDescendingStyle).TrackViewState();
        }

        #endregion State Management Methods

        #endregion Methods
    }
}